#include "znx_openssl.h"
#include "comm/znx_time.h"
#include "comm/znx_string.h"
#include "event/znx_event_loop.h"

int znx_ssl_conn_index;
int znx_ssl_server_conf_index;
int znx_ssl_conf_index;
int znx_ssl_session_ticket_keys_index;
int znx_ssl_certificate_index;
int znx_ssl_next_certificate_index;
int znx_ssl_certificate_name_index;
int znx_ssl_stapling_index;


static void znx_ssl_info_callback(const SSL *ssl, int where, int ret);
RSA *znx_ssl_rsa512_key_callback(SSL *ssl, int is_export,
    int key_length);
static int znx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store);

znx_bool_t
znx_ssl_init()
{
#if OPENSSL_VERSION_NUMBER >= 0x10100003L

    if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) == 0) {
        ZNX_DEF_LOG_ERROR("OPENSSL_init_ssl() failed");
        return ZNX_FALSE;
    }

    /*
     * OPENSSL_init_ssl() may leave errors in the error queue
     * while returning success
     */

    ERR_clear_error();

#else

    OPENSSL_config(NULL);

    SSL_library_init();
    SSL_load_error_strings();

    OpenSSL_add_all_algorithms();

#endif

#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
#ifndef SSL_OP_NO_COMPRESSION
    {
    /*
     * Disable gzip compression in OpenSSL prior to 1.0.0 version,
     * this saves about 522K per connection.
     */
    int                  n;
    STACK_OF(SSL_COMP)  *ssl_comp_methods;

    ssl_comp_methods = SSL_COMP_get_compression_methods();
    n = sk_SSL_COMP_num(ssl_comp_methods);

    while (n--) {
        (void) sk_SSL_COMP_pop(ssl_comp_methods);
    }
    }
#endif
#endif

    znx_ssl_conn_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
    if (znx_ssl_conn_index == -1) {
        ZNX_DEF_LOG_ERROR("SSL_get_ex_new_index() failed");
        return ZNX_FALSE;
    }

    znx_ssl_server_conf_index = SSL_CTX_get_ex_new_index(0,
        NULL, NULL, NULL, NULL);
    if (znx_ssl_server_conf_index == -1) {
        ZNX_DEF_LOG_ERROR("SSL_get_ex_new_index() failed");
        return ZNX_FALSE;
    }

    znx_ssl_conf_index = SSL_CTX_get_ex_new_index(0,
        NULL, NULL, NULL, NULL);
    if (znx_ssl_conf_index == -1) {
        ZNX_DEF_LOG_ERROR("SSL_get_ex_new_index() failed");
        return ZNX_FALSE;
    }

    znx_ssl_session_ticket_keys_index = SSL_CTX_get_ex_new_index(0,
        NULL, NULL, NULL, NULL);
    if (znx_ssl_session_ticket_keys_index == -1) {
        ZNX_DEF_LOG_ERROR("SSL_get_ex_new_index() failed");
        return ZNX_FALSE;
    }

    znx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0,
        NULL, NULL, NULL, NULL);
    if (znx_ssl_certificate_index == -1) {
        ZNX_DEF_LOG_ERROR("SSL_get_ex_new_index() failed");
        return ZNX_FALSE;
    }

    znx_ssl_next_certificate_index = X509_get_ex_new_index(0,
        NULL, NULL, NULL, NULL);
    if (znx_ssl_next_certificate_index == -1) {
        ZNX_DEF_LOG_ERROR("SSL_get_ex_new_index() failed");
        return ZNX_FALSE;
    }

    znx_ssl_certificate_name_index = X509_get_ex_new_index(0,
        NULL, NULL, NULL, NULL);
    if (znx_ssl_certificate_name_index == -1) {
        ZNX_DEF_LOG_ERROR("SSL_get_ex_new_index() failed");
        return ZNX_FALSE;
    }

    znx_ssl_stapling_index = X509_get_ex_new_index(0,
        NULL, NULL, NULL, NULL);
    if (znx_ssl_stapling_index == -1) {
        ZNX_DEF_LOG_ERROR("SSL_get_ex_new_index() failed");
        return ZNX_FALSE;
    }

    return ZNX_TRUE;
}


static znx_bool_t
znx_ssl_conf_init(znx_ssl_conf_t *ssl_conf, uint64_t protocols, void *data, char **err)
{
    ssl_conf->ctx = SSL_CTX_new(SSLv23_method());
    if (ssl_conf->ctx == NULL) {
        *err = "SSL_CTX_new() failed";
        return ZNX_FALSE;
    }

    if (SSL_CTX_set_ex_data(ssl_conf->ctx, znx_ssl_server_conf_index, data) == 0) {
        *err = "SSL_CTX_set_ex_data() failed";
        return ZNX_FALSE;
    }

    if (SSL_CTX_set_ex_data(ssl_conf->ctx, znx_ssl_conf_index, ssl_conf) == 0) {
        *err = "SSL_CTX_set_ex_data() failed";
        return ZNX_FALSE;
    }

    if (SSL_CTX_set_ex_data(ssl_conf->ctx, znx_ssl_certificate_index, NULL) == 0) {
        *err = "SSL_CTX_set_ex_data() failed";
        return ZNX_FALSE;
    }

    ssl_conf->buffer_size = ZNX_SSL_BUFSIZE;

    /* client side options */

#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_MICROSOFT_SESS_ID_BUG);
#endif

#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_NETSCAPE_CHALLENGE_BUG);
#endif

    /* server side options */

#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
#endif

#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER);
#endif

#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
    /* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_MSIE_SSLV2_RSA_PADDING);
#endif

#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_SSLEAY_080_CLIENT_DH_BUG);
#endif

#ifdef SSL_OP_TLS_D5_BUG
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_TLS_D5_BUG);
#endif

#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_TLS_BLOCK_PADDING_BUG);
#endif

#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
#endif


    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_SINGLE_DH_USE);

#if OPENSSL_VERSION_NUMBER >= 0x009080dfL
    /* only in 0.9.8m+ */
    SSL_CTX_clear_options(ssl_conf->ctx,
                          SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
#endif

    if (!(protocols & ZNX_SSL_SSLv2)) {
        SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_NO_SSLv2);
    }

    if (!(protocols & ZNX_SSL_SSLv3)) {
        SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_NO_SSLv3);
    }

    if (!(protocols & ZNX_SSL_TLSv1)) {
        SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_NO_TLSv1);
    }

#ifdef SSL_OP_NO_TLSv1_1
    SSL_CTX_clear_options(ssl_conf->ctx, SSL_OP_NO_TLSv1_1);
    if (!(protocols & ZNX_SSL_TLSv1_1)) {
        SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_NO_TLSv1_1);
    }
#endif

#ifdef SSL_OP_NO_TLSv1_2
    SSL_CTX_clear_options(ssl_conf->ctx, SSL_OP_NO_TLSv1_2);
    if (!(protocols & ZNX_SSL_TLSv1_2)) {
        SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_NO_TLSv1_2);
    }
#endif

#ifdef SSL_OP_NO_TLSv1_3
    SSL_CTX_clear_options(ssl_conf->ctx, SSL_OP_NO_TLSv1_3);
    if (!(protocols & ZNX_SSL_TLSv1_3)) {
        SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_NO_TLSv1_3);
    }
#endif

#ifdef SSL_CTX_set_min_proto_version
    SSL_CTX_set_min_proto_version(ssl_conf->ctx, 0);
    SSL_CTX_set_max_proto_version(ssl_conf->ctx, TLS1_2_VERSION);
#endif

#ifdef TLS1_3_VERSION
    SSL_CTX_set_min_proto_version(ssl_conf->ctx, 0);
    SSL_CTX_set_max_proto_version(ssl_conf->ctx, TLS1_3_VERSION);
#endif

#ifdef SSL_OP_NO_COMPRESSION
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_NO_COMPRESSION);
#endif

#ifdef SSL_OP_NO_ANTI_REPLAY
    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_NO_ANTI_REPLAY);
#endif

#ifdef SSL_MODE_RELEASE_BUFFERS
    SSL_CTX_set_mode(ssl_conf->ctx, SSL_MODE_RELEASE_BUFFERS);
#endif

#ifdef SSL_MODE_NO_AUTO_CHAIN
    SSL_CTX_set_mode(ssl_conf->ctx, SSL_MODE_NO_AUTO_CHAIN);
#endif

#if (ZNX_SSL_ASYNC)
    if (ssl_conf->async_enable) {
        SSL_CTX_set_mode(ssl_conf->ctx, SSL_MODE_ASYNC);
    }
#endif

    SSL_CTX_set_read_ahead(ssl_conf->ctx, 1);

    SSL_CTX_set_info_callback(ssl_conf->ctx, znx_ssl_info_callback);

    return ZNX_TRUE;
}


znx_ssl_conf_t *
znx_ssl_conf_create(uint64_t protocols, void *data, char **err)
{
    znx_ssl_conf_t *ssl_conf = znx_thread_calloc(sizeof(znx_ssl_conf_t));
    if (!znx_ssl_conf_init(ssl_conf, protocols, data, err)) {
        znx_thread_free(ssl_conf);
        return NULL;
    }

    ssl_conf->pool = znx_pool_create();
    ssl_conf->passwords = znx_array_create(8, sizeof(znx_str_t));
    znx_rwlock_init(&ssl_conf->rwlock);
    ssl_conf->ticket_keys = znx_array_create(8, sizeof(znx_ssl_session_ticket_key_t));

    return ssl_conf;
}


void
znx_ssl_conf_destroy(znx_ssl_conf_t *ssl_conf)
{
    if (ssl_conf->ticket_keys) {
        znx_array_destroy(ssl_conf->ticket_keys);
        ssl_conf->ticket_keys = NULL;
    }

    if (ssl_conf->passwords) {
        znx_array_destroy(ssl_conf->passwords);
        ssl_conf->passwords = NULL;
    }

    if (ssl_conf->pool != NULL) {
        znx_pool_destroy(ssl_conf->pool);
        ssl_conf->pool = NULL;
    }

    X509  *cert, *next;

    cert = SSL_CTX_get_ex_data(ssl_conf->ctx, znx_ssl_certificate_index);

    while (cert) {
        next = X509_get_ex_data(cert, znx_ssl_next_certificate_index);
        X509_free(cert);
        cert = next;
    }

    SSL_CTX_free(ssl_conf->ctx);
}


static void
znx_ssl_info_callback(const SSL *ssl, int where, int ret)
{
    BIO             *rbio, *wbio;
    znx_conn_t      *c;

    c = znx_ssl_get_conn(ssl);

#ifndef SSL_OP_NO_RENEGOTIATION
    if ((where & SSL_CB_HANDSHAKE_START)
        && SSL_is_server(ssl))
    {
        if (c->ssl->handshaked) {
            c->ssl->renegotiation = 1;
            ZNX_DEF_LOG_DEBUG("SSL renegotiation");
        }
    }
#endif

    if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) {
        if (!c->ssl->handshake_buffer_set) {
            /*
             * By default OpenSSL uses 4k buffer during a handshake,
             * which is too low for long certificate chains and might
             * result in extra round-trips.
             *
             * To adjust a buffer size we detect that buffering was added
             * to write side of the connection by comparing rbio and wbio.
             * If they are different, we assume that it's due to buffering
             * added to wbio, and set buffer size.
             */
            rbio = SSL_get_rbio(ssl);
            wbio = SSL_get_wbio(ssl);

            if (rbio != wbio) {
                (void) BIO_set_write_buffer_size(wbio, ZNX_SSL_BUFSIZE);
                c->ssl->handshake_buffer_set = 1;
            }
        }
    }
}


STACK_OF(X509) *
znx_parse_pem_cert(const u_char *pem, size_t pem_len, char **err)
{
    BIO                 *bio;
    X509                *x509;
    u_long              n;
    STACK_OF(X509)      *chain;

    bio = BIO_new_mem_buf((char *)pem, (int)pem_len);
    if (bio == NULL) {
        *err = "BIO_new_mem_buf() failed";
        ERR_clear_error();
        return NULL;
    }

    x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
    if (x509 == NULL) {
        *err = "PEM_read_bio_X509_AUX() failed";
        BIO_free(bio);
        ERR_clear_error();
        return NULL;
    }

    chain = sk_X509_new_null();
    if (chain == NULL) {
        *err = "sk_X509_new_null() failed";
        X509_free(x509);
        BIO_free(bio);
        ERR_clear_error();
        return NULL;
    }

    if (sk_X509_push(chain, x509) == 0) {
        *err = "sk_X509_push() failed";
        sk_X509_free(chain);
        X509_free(x509);
        BIO_free(bio);
        ERR_clear_error();
        return NULL;
    }

    /* read rest of the chain */

    for ( ;; ) {

        x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
        if (x509 == NULL) {
            n = ERR_peek_last_error();

            if (ERR_GET_LIB(n) == ERR_LIB_PEM
                && ERR_GET_REASON(n) == PEM_R_NO_START_LINE)
            {
                /* end of file */
                ERR_clear_error();
                break;
            }

            /* some real error */

            *err = "PEM_read_bio_X509() failed";
            sk_X509_pop_free(chain, X509_free);
            BIO_free(bio);
            ERR_clear_error();
            return NULL;
        }

        if (sk_X509_push(chain, x509) == 0) {
            *err = "sk_X509_push() failed";
            sk_X509_pop_free(chain, X509_free);
            X509_free(x509);
            BIO_free(bio);
            ERR_clear_error();
            return NULL;
        }
    }

    BIO_free(bio);

    return chain;
}


void
znx_free_cert(STACK_OF(X509) *chain)
{
    sk_X509_pop_free(chain, X509_free);
}


static int
znx_ssl_password_callback(char *buf, int size, int rwflag, void *userdata)
{
    znx_str_t *pwd = userdata;

    if (rwflag) {
        return 0;
    }

    if (pwd == NULL) {
        return 0;
    }

    if (pwd->len > (size_t) size) {
        ZNX_DEF_LOG_ERROR("password is truncated to %d bytes", size);
    } else {
        size = (int)pwd->len;
    }

    znx_memcpy(buf, pwd->data, (size_t)size);

    return size;
}


EVP_PKEY *
znx_parse_pem_priv_key(const u_char *pem_key, size_t pem_key_len,
    znx_array_t *passwords, char **err)
{
    BIO                 *bio;
    EVP_PKEY            *pkey;
    size_t              tries;
    pem_password_cb     *cb;
    znx_str_t           *pwd;

    bio = BIO_new_mem_buf((char *) pem_key, (int) pem_key_len);
    if (bio == NULL) {
        *err = "BIO_new_mem_buf() failed";
        ERR_clear_error();
        return NULL;
    }

    if (passwords && passwords->nelts > 0) {
        tries = passwords->nelts;
        pwd = passwords->elts;
        cb = znx_ssl_password_callback;
    } else {
        tries = 1;
        pwd = NULL;
        cb = NULL;
    }

    for ( ;; ) {
        pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd);
        if (pkey != NULL) {
            break;
        }

        if (tries-- > 1) {
            ERR_clear_error();
            (void) BIO_reset(bio);
            pwd++;
            continue;
        }

        *err = "PEM_read_bio_PrivateKey() failed";
        BIO_free(bio);
        ERR_clear_error();
        return NULL;
    }

    BIO_free(bio);

    return pkey;
}


void
znx_free_priv_key(EVP_PKEY *pkey)
{
    EVP_PKEY_free(pkey);
}


znx_bool_t
znx_ssl_set_cert(SSL *ssl, STACK_OF(X509) *chain, char **err)
{
#ifdef LIBRESSL_VERSION_NUMBER
    *err = "LibreSSL not supported";
    return ZNX_FALSE;
#endif

#if OPENSSL_VERSION_NUMBER < 0x1000205fL
    *err = "at least OpenSSL 1.0.2e required but found " OPENSSL_VERSION_TEXT;
    return ZNX_FALSE;
#endif

#ifdef OPENSSL_IS_BORINGSSL
    size_t          i;
#else
    int             i;
#endif

    X509              *x509 = NULL;

    if (ssl == NULL) {
        *err = "bad ssl";
        return ZNX_FALSE;
    }

    if (sk_X509_num(chain) < 1) {
        *err = "invalid certificate chain";
        ERR_clear_error();
        return ZNX_FALSE;
    }

    x509 = sk_X509_value(chain, 0);
    if (x509 == NULL) {
        *err = "sk_X509_value() failed";
        ERR_clear_error();
        return ZNX_FALSE;
    }

    if (SSL_use_certificate(ssl, x509) == 0) {
        *err = "SSL_use_certificate() failed";
        ERR_clear_error();
        return ZNX_FALSE;
    }

    x509 = NULL;

    /* read rest of the chain */

    for (i = 1; i < sk_X509_num(chain); i++) {
        x509 = sk_X509_value(chain, i);
        if (x509 == NULL) {
            *err = "sk_X509_value() failed";
            ERR_clear_error();
            return ZNX_FALSE;
        }

        if (SSL_add1_chain_cert(ssl, x509) == 0) {
            *err = "SSL_add1_chain_cert() failed";
            ERR_clear_error();
            return ZNX_FALSE;
        }
    }

    *err = NULL;
    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_conn_set_chain(znx_ssl_conn_t *conn, STACK_OF(X509) * chain, char **err)
{
    if (conn == NULL) {
        *err = "bad ssl_conn";
        return ZNX_FALSE;
    }

    return znx_ssl_set_cert(conn->ssl, chain, err);
}


znx_bool_t
znx_ssl_set_priv_key(SSL *ssl, EVP_PKEY *pkey, char **err)
{
    if (ssl == NULL) {
        *err = "bad ssl";
        return ZNX_FALSE;
    }

    if (SSL_use_PrivateKey(ssl, pkey) == 0) {
        ERR_clear_error();
        return ZNX_FALSE;
    }

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_conn_set_priv_key(znx_ssl_conn_t *conn, EVP_PKEY *pkey, char **err)
{
    if (conn == NULL) {
        *err = "bad ssl_conn";
        return ZNX_FALSE;
    }

    return znx_ssl_set_priv_key(conn->ssl, pkey, err);
}


znx_bool_t
znx_ssl_ciphers(znx_ssl_conf_t *ssl_conf, const u_char *ciphers,
    znx_bool_t prefer_server_ciphers, char **err)
{
    if (SSL_CTX_set_cipher_list(ssl_conf->ctx, (char *)ciphers) == 0) {
        *err = "SSL_CTX_set_cipher_list failed";
        return ZNX_FALSE;
    }

    if (prefer_server_ciphers) {
        SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
    }

#if (OPENSSL_VERSION_NUMBER < 0x10100001L && !defined LIBRESSL_VERSION_NUMBER)
    /* a temporary 512-bit RSA key is required for export versions of MSIE */
    SSL_CTX_set_tmp_rsa_callback(ssl_conf->ctx, znx_ssl_rsa512_key_callback);
#endif

    return ZNX_TRUE;
}


RSA *
znx_ssl_rsa512_key_callback(SSL *ssl, int is_export,
    int key_length)
{
    static RSA  *key;

    if (key_length != 512) {
        return NULL;
    }

#if (OPENSSL_VERSION_NUMBER < 0x10100003L && !defined OPENSSL_NO_DEPRECATED)

    if (key == NULL) {
        key = RSA_generate_key(512, RSA_F4, NULL, NULL);
    }

#endif

    return key;
}


static STACK_OF(X509) *
znx_ssl_client_certificate_parse(BIO *bio, char **err)
{
    X509            *x509;
    STACK_OF(X509)  *chain;
    u_long           n;

    x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
    if (x509 == NULL) {
        *err = "PEM_read_bio_X509_AUX() failed";
        ERR_clear_error();
        return NULL;
    }

    chain = sk_X509_new_null();
    if (chain == NULL) {
        *err = "sk_X509_new_null() failed";
        X509_free(x509);
        ERR_clear_error();
        return NULL;
    }

    if (sk_X509_push(chain, x509) == 0) {
        *err = "sk_X509_push() failed";
        sk_X509_free(chain);
        X509_free(x509);
        ERR_clear_error();
        return NULL;
    }

    /* read rest of the chain */
    for ( ;; ) {

        x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
        if (x509 == NULL) {
            n = ERR_peek_last_error();

            if (ERR_GET_LIB(n) == ERR_LIB_PEM
                && ERR_GET_REASON(n) == PEM_R_NO_START_LINE)
            {
                /* end of file */
                ERR_clear_error();
                break;
            }

            /* some real error */

            *err = "PEM_read_bio_X509() failed";
            sk_X509_pop_free(chain, X509_free);
            ERR_clear_error();
            return NULL;
        }

        if (sk_X509_push(chain, x509) == 0) {
            *err = "sk_X509_push() failed";
            sk_X509_pop_free(chain, X509_free);
            X509_free(x509);
            ERR_clear_error();
            return NULL;
        }
    }

    return chain;
}


znx_bool_t
znx_ssl_client_certificate_set(znx_ssl_conf_t *ssl_conf, STACK_OF(X509) *ca_certs, char **err)
{
    STACK_OF(X509)              *chain = ca_certs;
    STACK_OF(X509_NAME)         *name_chain = NULL;
    X509                        *x509 = NULL;
    X509_NAME                   *subject = NULL;
    X509_STORE                  *ca_store = NULL;

#ifdef OPENSSL_IS_BORINGSSL
    size_t                      i;
#else
    int                         i;
#endif

    if (chain != NULL) {
        ca_store = X509_STORE_new();
        if (ca_store == NULL) {
            *err = "X509_STORE_new() failed";
            return ZNX_FALSE;
        }

        /* construct name chain */
        name_chain = sk_X509_NAME_new_null();
        if (name_chain == NULL) {
            *err = "sk_X509_NAME_new_null() failed";
            goto failed;
        }

        for (i = 0; i < sk_X509_num(chain); i++) {
            x509 = sk_X509_value(chain, i);
            if (x509 == NULL) {
                *err = "sk_X509_value() failed";
                goto failed;
            }

            /* add subject to name chain, which will be sent to client */
            subject = X509_NAME_dup(X509_get_subject_name(x509));
            if (subject == NULL) {
                *err = "X509_get_subject_name() failed";
                goto failed;
            }

            if (!sk_X509_NAME_push(name_chain, subject)) {
                *err = "sk_X509_NAME_push() failed";
                X509_NAME_free(subject);
                goto failed;
            }

            /* add to trusted CA store */
            if (X509_STORE_add_cert(ca_store, x509) == 0) {
                *err = "X509_STORE_add_cert() failed";
                goto failed;
            }
        }

        if (SSL_CTX_set0_verify_cert_store(ssl_conf->ctx, ca_store) == 0) {
            *err = "SSL_set0_verify_cert_store() failed";
            goto failed;
        }

        SSL_CTX_set_client_CA_list(ssl_conf->ctx, name_chain);
    }

    return ZNX_TRUE;

failed:
    sk_X509_NAME_free(name_chain);
    X509_STORE_free(ca_store);

    return ZNX_FALSE;
}


znx_bool_t
znx_ssl_client_certificate(znx_ssl_conf_t *ssl_conf,
    znx_str_t *cert_str, int depth, char **err)
{
    SSL_CTX_set_verify(ssl_conf->ctx, SSL_VERIFY_PEER, znx_ssl_verify_callback);

    SSL_CTX_set_verify_depth(ssl_conf->ctx, depth);

    if (cert_str == NULL || cert_str->len == 0) {
        return ZNX_TRUE;
    }

    BIO                 *bio;
    bio = BIO_new_mem_buf((char *)cert_str->data, (int)cert_str->len);
    if (bio == NULL) {
        *err = "create mem bio failed";
        ERR_clear_error();
        return ZNX_FALSE;
    }

    STACK_OF(X509)      *chain = NULL;
    znx_bool_t          res;

    chain = znx_ssl_client_certificate_parse(bio, err);
    if (chain == NULL) {
        *err = "client cert parse failed";
        BIO_free(bio);
        return ZNX_FALSE;
    }

    res = znx_ssl_client_certificate_set(ssl_conf, chain, err);

    sk_X509_pop_free(chain, X509_free);
    BIO_free(bio);

    if (!res) {
        *err = "set client cert failed";
        return ZNX_FALSE;
    }


    return ZNX_TRUE;
}


static int
znx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store)
{
#if (ZNX_DEBUG)
    char                *subject, *issuer;
    int                 err, depth;
    X509                *cert;
    X509_NAME           *sname, *iname;
    znx_conn_t          *c;
    SSL                 *ssl;

    ssl = X509_STORE_CTX_get_ex_data(x509_store,
                                          SSL_get_ex_data_X509_STORE_CTX_idx());

    c = znx_ssl_get_conn(ssl);

    cert = X509_STORE_CTX_get_current_cert(x509_store);
    err = X509_STORE_CTX_get_error(x509_store);
    depth = X509_STORE_CTX_get_error_depth(x509_store);

    sname = X509_get_subject_name(cert);
    subject = sname ? X509_NAME_oneline(sname, NULL, 0) : "(none)";

    iname = X509_get_issuer_name(cert);
    issuer = iname ? X509_NAME_oneline(iname, NULL, 0) : "(none)";


    ZNX_DEF_LOG_ERROR("verify:%d, error:%d, depth:%d, "
                        "subject:\"%s\", issuer:\"%s\"",
                        ok, err, depth, subject, issuer);

    if (sname) {
        OPENSSL_free(subject);
    }

    if (iname) {
        OPENSSL_free(issuer);
    }
#endif

    return 1;
}


znx_bool_t
znx_ssl_trusted_certificate(znx_ssl_conf_t *ssl_conf,
    znx_str_t *cert_str, int depth, char **err)
{
    SSL_CTX_set_verify_depth(ssl_conf->ctx, depth);

    if (cert_str == NULL || cert_str->len == 0) {
        return ZNX_TRUE;
    }

    BIO                 *bio;
    bio = BIO_new_mem_buf((char *)cert_str->data, (int)cert_str->len);
    if (bio == NULL) {
        *err = "create mem bio failed";
        ERR_clear_error();
        return ZNX_FALSE;
    }

    STACK_OF(X509)      *chain = NULL;
    chain = znx_ssl_client_certificate_parse(bio, err);
    if (chain == NULL) {
        *err = "client cert parse failed";
        BIO_free(bio);
        return ZNX_FALSE;
    }

    X509_STORE          *ca_store = NULL;
    ca_store = X509_STORE_new();
    if (ca_store == NULL) {
        *err = "X509_STORE_new() failed";
        return ZNX_FALSE;
    }

#ifdef OPENSSL_IS_BORINGSSL
    size_t              i;
#else
    int                 i;
#endif

    X509                *x509 = NULL;

    for (i = 0; i < sk_X509_num(chain); i++) {
        x509 = sk_X509_value(chain, i);
        if (x509 == NULL) {
            *err = "sk_X509_value() failed";
            goto failed;
        }

        /* add to trusted CA store */
        if (X509_STORE_add_cert(ca_store, x509) == 0) {
            *err = "X509_STORE_add_cert() failed";
            goto failed;
        }
    }

    if (SSL_CTX_set0_verify_cert_store(ssl_conf->ctx, ca_store) == 0) {
        *err = "SSL_set0_verify_cert_store() failed";
        goto failed;
    }

    return ZNX_TRUE;

failed:
    X509_STORE_free(ca_store);

    return ZNX_FALSE;
}


znx_bool_t
znx_ssl_crl(znx_ssl_conf_t *ssl_conf, znx_str_t *crl, char **err)
{
    X509_STORE   *store;
    X509_LOOKUP  *lookup;

    if (crl->len == 0) {
        return ZNX_TRUE;
    }

    store = SSL_CTX_get_cert_store(ssl_conf->ctx);
    if (store == NULL) {
        *err = "SSL_CTX_get_cert_store() failed";
        return ZNX_FALSE;
    }

    lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
    if (lookup == NULL) {
        *err = "X509_STORE_add_lookup() failed";
        return ZNX_FALSE;
    }

    if (X509_LOOKUP_load_file(lookup, (char *)crl->data, X509_FILETYPE_PEM)
        == 0)
    {
        *err = "X509_LOOKUP_load_file() failed";
        return ZNX_FALSE;
    }

    X509_STORE_set_flags(store,
        X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_dhparam(znx_ssl_conf_t *ssl_conf, znx_str_t *file, char **err)
{
    DH   *dh;
    BIO  *bio;

    if (file->len == 0) {
        return ZNX_TRUE;
    }

    bio = BIO_new_file((char *)file->data, "r");
    if (bio == NULL) {
        *err = "BIO_new_file failed";
        return ZNX_FALSE;
    }

    dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
    if (dh == NULL) {
        *err = "PEM_read_bio_DHparams failed";
        BIO_free(bio);
        return ZNX_FALSE;
    }

    SSL_CTX_set_tmp_dh(ssl_conf->ctx, dh);

    DH_free(dh);
    BIO_free(bio);

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_ecdh_curve(znx_ssl_conf_t *ssl_conf,
    znx_str_t *name, char **err)
{
#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
#ifndef OPENSSL_NO_ECDH

    /*
     * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
     * from RFC 4492 section 5.1.1, or explicitly described curves over
     * binary fields.  OpenSSL only supports the "named curves", which provide
     * maximum interoperability.
     */

#if (defined SSL_CTX_set1_curves_list || defined SSL_CTRL_SET_CURVES_LIST)

    /*
     * OpenSSL 1.0.2+ allows configuring a curve list instead of a single
     * curve previously supported.  By default an internal list is used,
     * with prime256v1 being preferred by server in OpenSSL 1.0.2b+
     * and X25519 in OpenSSL 1.1.0+.
     *
     * By default a curve preferred by the client will be used for
     * key exchange.  The SSL_OP_CIPHER_SERVER_PREFERENCE option can
     * be used to prefer server curves instead, similar to what it
     * does for ciphers.
     */

    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_SINGLE_ECDH_USE);

#if SSL_CTRL_SET_ECDH_AUTO
    /* not needed in OpenSSL 1.1.0+ */
    SSL_CTX_set_ecdh_auto(ssl_conf->ctx, 1);
#endif

    if (name->len == 4 && znx_strcmp(name->data, "auto") == 0) {
        return ZNX_TRUE;
    }

    if (SSL_CTX_set1_curves_list(ssl_conf->ctx, name->data) == 0) {
        *err = "SSL_CTX_set1_curves_list failed";
        return ZNX_FALSE;
    }

#else

    int      nid;
    char    *curve;
    EC_KEY  *ecdh;

    if (znx_strcmp(name->data, "auto") == 0) {
        curve = "prime256v1";

    } else {
        curve = (char *)name->data;
    }

    nid = OBJ_sn2nid(curve);
    if (nid == 0) {
        *err = "OBJ_sn2nid failed, unknown curve";
        return ZNX_FALSE;
    }

    ecdh = EC_KEY_new_by_curve_name(nid);
    if (ecdh == NULL) {
        *ecdh = "EC_KEY_new_by_curve_name failed";
        return ZNX_FALSE;
    }

    SSL_CTX_set_options(ssl_conf->ctx, SSL_OP_SINGLE_ECDH_USE);

    SSL_CTX_set_tmp_ecdh(ssl_conf->ctx, ecdh);

    EC_KEY_free(ecdh);
#endif
#endif
#endif

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_conf_commands(znx_ssl_conf_t *ssl_conf,
    znx_array_t *commands, char **err)
{
    if (commands == NULL) {
        return ZNX_TRUE;
    }

#ifdef SSL_CONF_FLAG_FILE
    {
    u_char          *key, *value;
    size_t          i;
    znx_keyval_t    *cmd;
    SSL_CONF_CTX  *cctx;

    cctx = SSL_CONF_CTX_new();
    if (cctx == NULL) {
        *err = "SSL_CONF_CTX_new() failed";
        return ZNX_FALSE;
    }

    SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE);
    SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SERVER);
    SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CLIENT);
    SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE);
    SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SHOW_ERRORS);

    SSL_CONF_CTX_set_ssl_ctx(cctx, ssl_conf->ctx);

    cmd = commands->elts;
    for (i = 0; i < commands->nelts; i++) {

        key = cmd[i].key.data;
#if 0
        int             type;
        type = SSL_CONF_cmd_value_type(cctx, key);
        if (type == SSL_CONF_TYPE_FILE || type == SSL_CONF_TYPE_DIR) {
        }
#endif

        value = cmd[i].value.data;

        if (SSL_CONF_cmd(cctx, (char *)key, (char *)value) <= 0) {
            *err = "SSL_CONF_cmd() failed";
            SSL_CONF_CTX_free(cctx);
            return ZNX_FALSE;
        }
    }

    if (SSL_CONF_CTX_finish(cctx) != 1) {
        *err = "SSL_CONF_finish() failed";
        SSL_CONF_CTX_free(cctx);
        return ZNX_FALSE;
    }

    SSL_CONF_CTX_free(cctx);

    return ZNX_TRUE;
    }
#else

    *err = "SSL_CONF_cmd() is not available on this platform";
    return ZNX_FALSE;
#endif
}


static znx_bool_t
znx_ssl_session_id_context(znx_ssl_conf_t *ssl_conf, znx_str_t *sess_ctx,
    znx_array_t *certificates, char **err)
{
    int                     n, i;
    X509                    *cert;
    X509_NAME               *name;
    znx_str_t               *certs;
    size_t                  k;
    EVP_MD_CTX              *md;
    unsigned int            len;
    STACK_OF(X509_NAME)     *list;
    u_char                  buf[EVP_MAX_MD_SIZE];

    /*
     * Session ID context is set based on the string provided,
     * the server certificates, and the client CA list.
     */

    md = EVP_MD_CTX_create();
    if (md == NULL) {
        *err = "EVP_MD_CTX_create failed";
        return ZNX_FALSE;
    }

    if (EVP_DigestInit_ex(md, EVP_sha1(), NULL) == 0) {
        *err = "EVP_DigestInit_ex() failed";
        goto failed;
    }

    if (EVP_DigestUpdate(md, sess_ctx->data, sess_ctx->len) == 0) {
        *err = "EVP_DigestUpdate() failed";
        goto failed;
    }

    for (cert = SSL_CTX_get_ex_data(ssl_conf->ctx, znx_ssl_certificate_index);
         cert;
         cert = X509_get_ex_data(cert, znx_ssl_next_certificate_index))
    {
        if (X509_digest(cert, EVP_sha1(), buf, &len) == 0) {
            *err = "X509_digest() failed";
            goto failed;
        }

        if (EVP_DigestUpdate(md, buf, len) == 0) {
            *err = "EVP_DigestUpdate() failed";
            goto failed;
        }
    }

    if (SSL_CTX_get_ex_data(ssl_conf->ctx, znx_ssl_certificate_index) == NULL) {

        /*
         * If certificates are loaded dynamically, we use certificate
         * names as specified in the configuration (with variables).
         */

        certs = certificates->elts;
        for (k = 0; k < certificates->nelts; k++) {

            if (EVP_DigestUpdate(md, certs[k].data, certs[k].len) == 0) {
                *err = "EVP_DigestUpdate() failed";
                goto failed;
            }
        }
    }

    list = SSL_CTX_get_client_CA_list(ssl_conf->ctx);

    if (list != NULL) {
        n = sk_X509_NAME_num(list);

        for (i = 0; i < n; i++) {
            name = sk_X509_NAME_value(list, i);

            if (X509_NAME_digest(name, EVP_sha1(), buf, &len) == 0) {
                *err = "X509_NAME_digest() failed";
                goto failed;
            }

            if (EVP_DigestUpdate(md, buf, len) == 0) {
                *err = "EVP_DigestUpdate() failed";
                goto failed;
            }
        }
    }

    if (EVP_DigestFinal_ex(md, buf, &len) == 0) {
        *err = "EVP_DigestFinal_ex() failed";
        goto failed;
    }

    EVP_MD_CTX_destroy(md);

    if (SSL_CTX_set_session_id_context(ssl_conf->ctx, buf, len) == 0) {
        *err = "SSL_CTX_set_session_id_context() failed";
        return ZNX_FALSE;
    }

    return ZNX_TRUE;

failed:

    EVP_MD_CTX_destroy(md);

    return ZNX_FALSE;
}


static int
znx_ssl_new_session(SSL *ssl, SSL_SESSION *ssl_session)
{
    int                     len;
    u_char                  buf[ZNX_SSL_MAX_SESSION_SIZE] = { 0 };
    znx_conn_t              *c;
    SSL_CTX                 *ssl_ctx;
    const u_char            *session_id;
    unsigned int            session_id_length;
    znx_ssl_session_id_t    *sess_id;
    u_char                  *p;


#ifdef TLS1_3_VERSION

    /*
     * OpenSSL tries to save TLSv1.3 sessions into session cache
     * even when using tickets for stateless session resumption,
     * "because some applications just want to know about the creation
     * of a session"; do not cache such sessions
     */

    if (SSL_version(ssl) == TLS1_3_VERSION
        && (SSL_get_options(ssl) & SSL_OP_NO_TICKET) == 0)
    {
        return 0;
    }

#endif

    p = buf;
    len = i2d_SSL_SESSION(ssl_session, &p);

    /* do not cache too big session */
    if (len > (int) ZNX_SSL_MAX_SESSION_SIZE) {
        return 0;
    }

    session_id = SSL_SESSION_get_id(ssl_session, &session_id_length);

    if (session_id_length > 32) {
        return 0;
    }

    c = znx_ssl_get_conn(ssl);

    ssl_ctx = c->ssl->ssl_ctx;

    int64_t expires_msec = znx_usec_2_msec(get_cache_time()) +
        (SSL_CTX_get_timeout(ssl_ctx) * 1000);
    sess_id = znx_ssl_session_id_create((u_char *)session_id,
        session_id_length, buf, (size_t)len, expires_msec);
    znx_ssl_session_cache_put(sess_id);

    return 0;
}


static SSL_SESSION *
znx_ssl_get_cached_session(SSL *ssl,
#if OPENSSL_VERSION_NUMBER >= 0x10100003L
    const
#endif
    u_char *id, int len, int *copy)
{
    *copy = 0;

    znx_ssl_session_id_t target;
    target.crc = znx_crc32_long(id, (size_t)len);
    znx_memcpy(target.id, id, (size_t)len);
    target.id_len = (size_t)len;

    return znx_ssl_session_cache_get(&target);
}


static void
znx_ssl_remove_session(SSL_CTX *ssl, SSL_SESSION *ssl_session)
{
    const u_char            *id;
    unsigned int            len;
    znx_ssl_session_id_t    target;

    id = SSL_SESSION_get_id(ssl_session, &len);
    target.crc = znx_crc32_long(id, len);
    znx_memcpy(target.id, id, len);
    target.id_len = (size_t)len;

    znx_ssl_session_cache_remove(&target);
}


void
znx_ssl_remove_cached_session(SSL_CTX *ssl_ctx, SSL_SESSION *ssl_session)
{
    SSL_CTX_remove_session(ssl_ctx, ssl_session);
    znx_ssl_remove_session(ssl_ctx, ssl_session);
}


znx_bool_t
znx_ssl_session_cache(znx_ssl_conf_t *ssl_conf, znx_str_t *sess_ctx,
    znx_array_t *certificates, znx_bool_t enable,
    int64_t timeout_ms, char **err)
{
    long timeout_sec = timeout_ms / 1000;

    SSL_CTX_set_timeout(ssl_conf->ctx, (long)timeout_sec);

    if (!znx_ssl_session_id_context(ssl_conf, sess_ctx, certificates, err)) {
        return ZNX_FALSE;
    }

    if (!enable) {
        SSL_CTX_set_session_cache_mode(ssl_conf->ctx, SSL_SESS_CACHE_OFF);
        return ZNX_TRUE;
    }

    long cache_mode = SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL;
    SSL_CTX_set_session_cache_mode(ssl_conf->ctx, cache_mode);

    SSL_CTX_sess_set_new_cb(ssl_conf->ctx, znx_ssl_new_session);
    SSL_CTX_sess_set_get_cb(ssl_conf->ctx, znx_ssl_get_cached_session);
    SSL_CTX_sess_set_remove_cb(ssl_conf->ctx, znx_ssl_remove_session);

    return ZNX_TRUE;
}


#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB


static int
znx_ssl_ticket_key_callback(SSL *ssl,
    unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
    HMAC_CTX *hctx, int enc)
{
    znx_conn_t                      *c;
    SSL_CTX                         *ssl_ctx;
    znx_ssl_conf_t                  *ssl_conf;
    const EVP_MD                    *digest;
    const EVP_CIPHER                *cipher;
    znx_ssl_session_ticket_key_t    ticket_key;
    znx_ssl_session_ticket_key_t    *ticket_keys;
    uint32_t                        size;

    c = znx_ssl_get_conn(ssl);
    ssl_ctx = c->ssl->ssl_ctx;

    // TODO: ssl_rotate_ticket_keys.

#ifdef OPENSSL_NO_SHA256
    digest = EVP_sha1();
#else
    digest = EVP_sha256();
#endif

    ssl_conf = SSL_CTX_get_ex_data(ssl_ctx, znx_ssl_conf_index);
    if (ssl_conf == NULL) {
        return -1;
    }

    if (enc == 1) {
        /* encrypt session ticket */

        znx_rwlock_rlock(&ssl_conf->rwlock);
        ticket_keys = ssl_conf->ticket_keys->elts;
        ticket_key = ticket_keys[0];
        znx_rwlock_runlock(&ssl_conf->rwlock);

        if (ticket_key.size == 48) {
            cipher = EVP_aes_128_cbc();
            size = 16;
        } else {
            cipher = EVP_aes_256_cbc();
            size = 32;
        }

        if (RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)) != 1) {
            ZNX_DEF_LOG_WARN("RAND_bytes() failed");
            return -1;
        }

        if (EVP_EncryptInit_ex(ectx, cipher, NULL,
            ticket_key.aes_key, iv) != 1) {
            ZNX_DEF_LOG_WARN("EVP_EncryptInit_ex() failed");
            return -1;
        }

#if OPENSSL_VERSION_NUMBER >= 0x10000000L
        if (HMAC_Init_ex(hctx, ticket_key.hmac_key, (int)size, digest, NULL) != 1) {
            ZNX_DEF_LOG_WARN("HMAC_Init_ex() failed");
            return -1;
        }
#else
        HMAC_Init_ex(hctx, ticket_key.hmac_key, (int)size, digest, NULL);
#endif

        znx_memcpy(name, ticket_key.name, 16);

        return 1;
    } else {
        /* decrypt session ticket */

        znx_bool_t found = ZNX_FALSE;
        znx_rwlock_rlock(&ssl_conf->rwlock);
        ticket_keys = ssl_conf->ticket_keys->elts;
        for (size_t i = 0; i < ssl_conf->ticket_keys->nelts; i++) {
            if (znx_memcmp(name, ticket_keys[i].name, 16) == 0) {
                ticket_key = ticket_keys[i];
                found = ZNX_TRUE;
                break;
            }
        }
        znx_rwlock_runlock(&ssl_conf->rwlock);

        if (!found) {
            return 0;
        }

        if (ticket_key.size == 48) {
            cipher = EVP_aes_128_cbc();
            size = 16;
        } else {
            cipher = EVP_aes_256_cbc();
            size = 32;
        }

#if OPENSSL_VERSION_NUMBER >= 0x10000000L
        if (HMAC_Init_ex(hctx, ticket_key.hmac_key, (int)size, digest, NULL) != 1) {
            ZNX_DEF_LOG_ERROR("HMAC_Init_ex() failed");
            return -1;
        }
#else
        HMAC_Init_ex(hctx, ticket_key.hmac_key, (int)size, digest, NULL);
#endif

        if (EVP_DecryptInit_ex(ectx, cipher, NULL, ticket_key.aes_key, iv) != 1) {
            ZNX_DEF_LOG_ERROR("EVP_DecryptInit_ex() failed");
            return -1;
        }

        /* renew if TLSv1.3 */

#ifdef TLS1_3_VERSION
        if (SSL_version(ssl) == TLS1_3_VERSION) {
            return 2;
        }
#endif
        return 1;
    }

    return 0;
}


znx_bool_t
znx_ssl_session_ticket_key(znx_ssl_conf_t *ssl_conf,
    znx_str_t *key, char **err)
{
    if (key->len != 48 && key->len != 80) {
        *err = "key len must be 48 or 80 byte";
        return ZNX_FALSE;
    }

    znx_ssl_session_ticket_key_t *ticket_key;
    ticket_key = znx_array_push(ssl_conf->ticket_keys);

    if (key->len == 48) {
        ticket_key->size = 48;
        znx_memcpy(ticket_key->name, key->data, 16);
        znx_memcpy(ticket_key->aes_key, key->data + 16, 16);
        znx_memcpy(ticket_key->hmac_key, key->data + 32, 16);
    } else {
        ticket_key->size = 80;
        znx_memcpy(ticket_key->name, key->data, 16);
        znx_memcpy(ticket_key->aes_key, key->data + 16, 32);
        znx_memcpy(ticket_key->hmac_key, key->data + 48, 32);
    }

    if (SSL_CTX_set_tlsext_ticket_key_cb(ssl_conf->ctx, znx_ssl_ticket_key_callback)
        == 0)
    {
        ZNX_DEF_LOG_WARN("session ticket not supported, ignore");
        return ZNX_TRUE;
    }

    return ZNX_TRUE;
}

#else

znx_bool_t
znx_ssl_session_ticket_key(znx_ssl_conf_t *ssl_conf,
    znx_str_t *key, char **err)
{
    ZNX_DEF_LOG_ERROR("ssl_session_ticket_key ignore, not supported");
    return ZNX_TRUE;
}


#endif


znx_bool_t
znx_ssl_create_connection(znx_ssl_conf_t *ssl_conf, znx_conn_t *c,
    znx_conn_handle_pt handler, znx_bool_t client, char **err)
{
    znx_ssl_conn_t          *sc;

    sc = znx_pool_calloc(c->pool, sizeof(znx_ssl_conn_t));
    sc->ssl_ctx = ssl_conf->ctx;

    sc->ssl = SSL_new(ssl_conf->ctx);

    if (sc->ssl == NULL) {
        *err = "SSL_new() failed";
        return ZNX_FALSE;
    }

    if (SSL_set_fd(sc->ssl, c->fd) == 0) {
        *err = "SSL_set_fd() failed";
        return ZNX_FALSE;
    }

    if (client) {
        SSL_set_connect_state(sc->ssl);

    } else {
        SSL_set_accept_state(sc->ssl);

#ifdef SSL_OP_NO_RENEGOTIATION
        SSL_set_options(sc->ssl, SSL_OP_NO_RENEGOTIATION);
#endif
    }

    if (SSL_set_ex_data(sc->ssl, znx_ssl_conn_index, c) == 0) {
        *err = "SSL_set_ex_data() failed";
        return ZNX_FALSE;
    }

    c->ssl = sc;

    c->ssl->handler = handler;

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_set_session(znx_conn_t *c, SSL_SESSION *ssl_session, char **err)
{
    if (ssl_session) {
        if (SSL_set_session(c->ssl->ssl, ssl_session) == 0) {
            *err = "SSL_set_session() failed";
            return ZNX_FALSE;
        }
    }

    return ZNX_TRUE;
}

SSL_SESSION *
znx_ssl_get_session(znx_conn_t *c)
{
#ifdef TLS1_3_VERSION
    if (c->ssl->session) {
        SSL_SESSION_up_ref(c->ssl->session);
        return c->ssl->session;
    }
#endif

    return SSL_get1_session(c->ssl->ssl);
}

SSL_SESSION *
znx_ssl_get0_session(znx_conn_t *c)
{
    if (c->ssl->session) {
        return c->ssl->session;
    }

    return SSL_get0_session(c->ssl->ssl);
}

#ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT

static znx_bool_t
znx_ssl_check_name(znx_str_t *name, ASN1_STRING *pattern)
{
    u_char      *s, *p, *end;
    size_t      slen, plen;

    s = name->data;
    slen = name->len;

    p = ASN1_STRING_data(pattern);
    plen = (size_t)ASN1_STRING_length(pattern);

    if (slen == plen && znx_strncasecmp(s, p, plen) == 0) {
        return ZNX_TRUE;
    }

    if (plen > 2 && p[0] == '*' && p[1] == '.') {
        plen -= 1;
        p += 1;

        end = s + slen;
        s = znx_strlchr(s, end, '.');

        if (s == NULL) {
            return ZNX_FALSE;
        }

        slen = (size_t)(end - s);

        if (plen == slen && znx_strncasecmp(s, p, plen) == 0) {
            return ZNX_TRUE;
        }
    }

    return ZNX_FALSE;
}

#endif

znx_bool_t
znx_ssl_check_host(znx_conn_t *c, znx_str_t *name)
{
    X509   *cert;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_FALSE;
    }

#ifdef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT

    /* X509_check_host() is only available in OpenSSL 1.0.2+ */

    if (name->len == 0) {
        goto failed;
    }

    if (X509_check_host(cert, (char *)name->data, name->len, 0, NULL) != 1) {
        goto failed;
    }

    goto found;

#else

    {
        int                         n, i;
        X509_NAME                   *sname;
        ASN1_STRING                 *str;
        X509_NAME_ENTRY             *entry;
        GENERAL_NAME                *altname;
        STACK_OF(GENERAL_NAME)      *altnames;

        /*
        * As per RFC6125 and RFC2818, we check subjectAltName extension,
        * and if it's not present - commonName in Subject is checked.
        */

        altnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);

        if (altnames) {
            n = sk_GENERAL_NAME_num(altnames);

            for (i = 0; i < n; i++) {
                altname = sk_GENERAL_NAME_value(altnames, i);

                if (altname->type != GEN_DNS) {
                    continue;
                }

                str = altname->d.dNSName;

                if (znx_ssl_check_name(name, str) == ZNX_TRUE) {
                    GENERAL_NAMES_free(altnames);
                    goto found;
                }
            }

            GENERAL_NAMES_free(altnames);
            goto failed;
        }

        /*
        * If there is no subjectAltName extension, check commonName
        * in Subject.  While RFC2818 requires to only check "most specific"
        * CN, both Apache and OpenSSL check all CNs, and so do we.
        */

        sname = X509_get_subject_name(cert);

        if (sname == NULL) {
            goto failed;
        }

        i = -1;
        for ( ;; ) {
            i = X509_NAME_get_index_by_NID(sname, NID_commonName, i);

            if (i < 0) {
                break;
            }

            entry = X509_NAME_get_entry(sname, i);
            str = X509_NAME_ENTRY_get_data(entry);

            if (znx_ssl_check_name(name, str) == ZNX_TRUE) {
                goto found;
            }
        }
    }

#endif

failed:
    X509_free(cert);
    return ZNX_FALSE;

found:

    X509_free(cert);
    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_get_ciphers(znx_conn_t *c, znx_pool_t *pool, znx_str_t *s)
{
#ifdef SSL_CTRL_GET_RAW_CIPHERLIST

    int                 n, i, bytes;
    size_t              len;
    u_char              *ciphers, *p;
    const SSL_CIPHER    *cipher;

    bytes = (int)SSL_get0_raw_cipherlist(c->ssl->ssl, NULL);
    n = (int)SSL_get0_raw_cipherlist(c->ssl->ssl, &ciphers);

    if (n <= 0) {
        s->len = 0;
        return ZNX_TRUE;
    }

    len = 0;
    n /= bytes;

    for (i = 0; i < n; i++) {
        cipher = SSL_CIPHER_find(c->ssl->ssl, ciphers + i * bytes);

        if (cipher) {
            len += znx_strlen(SSL_CIPHER_get_name(cipher));

        } else {
            len += sizeof("0x") - 1 + (size_t)(bytes) * (sizeof("00") - 1);
        }

        len += sizeof(":") - 1;
    }

    s->data = znx_pool_malloc(pool, len + 1);

    p = s->data;

    for (i = 0; i < n; i++) {
        cipher = SSL_CIPHER_find(c->ssl->ssl, ciphers + i * bytes);

        if (cipher) {
            p = znx_sprintf(p, "%s", SSL_CIPHER_get_name(cipher));

        } else {
            p = znx_sprintf(p, "0x");
            p = znx_hex_dump(p, ciphers + i * bytes, (size_t)bytes);
        }

        *p++ = ':';
    }

    p--;

    s->len = (size_t)(p - s->data);

#else

    u_char  buf[4096];

    if (SSL_get_shared_ciphers(c->ssl->ssl, (char *)buf, 4096)
        == NULL)
    {
        s->len = 0;
        return ZNX_TRUE;
    }

    s->len = znx_strlen((char *)buf);
    s->data = znx_pool_malloc(pool, s->len);
    znx_memcpy(s->data, buf, s->len);

#endif

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_get_curves(znx_conn_t *c, znx_pool_t *pool, znx_str_t *s)
{
#ifdef SSL_CTRL_GET_CURVES

    int         *curves, n, i, nid;
    u_char      *p;
    size_t      len;

    n = (int)SSL_get1_curves(c->ssl->ssl, NULL);

    if (n <= 0) {
        s->len = 0;
        return ZNX_TRUE;
    }

    znx_g_buf_grow((size_t)n * sizeof(int));
    curves = (int *)g_buf;

    n = (int)SSL_get1_curves(c->ssl->ssl, curves);
    len = 0;

    for (i = 0; i < n; i++) {
        nid = curves[i];

        if (nid & TLSEXT_nid_unknown) {
            len += sizeof("0x0000") - 1;

        } else {
            len += znx_strlen(OBJ_nid2sn(nid));
        }

        len += sizeof(":") - 1;
    }

    s->data = znx_pool_calloc(pool, len + 1);

    p = s->data;

    for (i = 0; i < n; i++) {
        nid = curves[i];

        if (nid & TLSEXT_nid_unknown) {
            p = znx_sprintf(p, "0x%04xd", nid & 0xffff);

        } else {
            p = znx_sprintf(p, "%s", OBJ_nid2sn(nid));
        }

        *p++ = ':';
    }

    p--;

    s->len = (size_t)(p - s->data);

#else

    s->len = 0;

#endif

    return ZNX_TRUE;
}


znx_bool_t
ngx_ssl_get_session_id(znx_conn_t *c, znx_pool_t *pool, znx_str_t *s)
{
    u_char          *buf;
    SSL_SESSION     *sess;
    unsigned int    len;

    sess = SSL_get0_session(c->ssl->ssl);
    if (sess == NULL) {
        s->len = 0;
        return ZNX_TRUE;
    }

    buf = (u_char *) SSL_SESSION_get_id(sess, &len);

    s->len = 2 * len;
    s->data = znx_pool_calloc(pool, 2 * len);
    if (s->data == NULL) {
        return ZNX_FALSE;
    }

    znx_hex_dump(s->data, buf, len);

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_get_server_name(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s)
{
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME

    size_t          len;
    const char      *name;

    name = SSL_get_servername(c->ssl->ssl, TLSEXT_NAMETYPE_host_name);

    if (name) {
        len = znx_strlen(name);

        s->len = len;
        s->data = znx_pool_calloc(pool, len);
        if (s->data == NULL) {
            return ZNX_FALSE;
        }

        znx_memcpy(s->data, name, len);

        return ZNX_TRUE;
    }

#endif

    s->len = 0;
    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_get_client_hello_server_name(znx_conn_t *c,
    znx_str_t *s, char **err)
{
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
    size_t                  remaining, len;
    const u_char            *p;

    remaining = 0;

    /* This code block is taken from OpenSSL's client_hello_select_server_ctx()
     * */
    if (!SSL_client_hello_get0_ext(c->ssl->ssl, TLSEXT_TYPE_server_name, &p,
                                   &remaining))
    {
        *err = "SSL_client_hello_get0_ext() failed";
        return ZNX_FALSE;
    }

    if (remaining <= 2) {
        *err = "Bad SSL Client Hello Extension";
        return ZNX_FALSE;
    }

    len = (size_t)((*(p++) << 8));
    len += (size_t)(*(p++));
    if (len + 2 != remaining) {
        *err = "Bad SSL Client Hello Extension";
        return ZNX_FALSE;
    }

    remaining = len;
    if (remaining == 0 || *p++ != TLSEXT_NAMETYPE_host_name) {
        *err = "Bad SSL Client Hello Extension";
        return ZNX_FALSE;
    }

    remaining--;
    if (remaining <= 2) {
        *err = "Bad SSL Client Hello Extension";
        return ZNX_FALSE;
    }

    len = (size_t)(*(p++) << 8);
    len += (size_t)(*(p++));
    if (len + 2 > remaining) {
        *err = "Bad SSL Client Hello Extension";
        return ZNX_FALSE;
    }

    remaining = len;
    s->data = (u_char *)p;
    s->len = len;

    return ZNX_TRUE;


#else

    *err = "OpenSSL too old to support this function";

    return ZNX_FALSE;

#endif
}


znx_bool_t
znx_ssl_get_raw_certificate(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    size_t      len;
    BIO         *bio;
    X509        *cert;

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

    bio = BIO_new(BIO_s_mem());
    if (bio == NULL) {
        *err = "BIO_new() failed";
        X509_free(cert);
        return ZNX_FALSE;
    }

    if (PEM_write_bio_X509(bio, cert) == 0) {
        *err = "PEM_write_bio_X509() failed";
        goto failed;
    }

    len = (size_t)BIO_pending(bio);
    s->len = len;

    s->data = znx_pool_malloc(pool, len);

    BIO_read(bio, s->data, (int)len);

    BIO_free(bio);
    X509_free(cert);

    return ZNX_TRUE;

failed:

    BIO_free(bio);
    X509_free(cert);

    return ZNX_FALSE;
}


znx_bool_t
znx_ssl_get_subject_dn(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    BIO        *bio;
    X509       *cert;
    X509_NAME  *name;

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

    name = X509_get_subject_name(cert);
    if (name == NULL) {
        *err = "X509_get_subject_name() failed";
        X509_free(cert);
        return ZNX_FALSE;
    }

    bio = BIO_new(BIO_s_mem());
    if (bio == NULL) {
        *err = "BIO_new() failed";
        X509_free(cert);
        return ZNX_FALSE;
    }

    if (X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253) < 0) {
        *err = "X509_NAME_print_ex() failed";
        goto failed;
    }

    s->len = (size_t)BIO_pending(bio);
    s->data = znx_pool_malloc(pool, s->len);

    BIO_read(bio, s->data, (int)s->len);

    BIO_free(bio);
    X509_free(cert);

    return ZNX_TRUE;

failed:

    BIO_free(bio);
    X509_free(cert);

    return ZNX_FALSE;
}


znx_bool_t
znx_ssl_get_issuer_dn(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    BIO        *bio;
    X509       *cert;
    X509_NAME  *name;

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

    name = X509_get_issuer_name(cert);
    if (name == NULL) {
        X509_free(cert);
        *err = "X509_get_issuer_name() return nil";
        return ZNX_FALSE;
    }

    bio = BIO_new(BIO_s_mem());
    if (bio == NULL) {
        X509_free(cert);
        *err = "BIO_new() failed";
        return ZNX_FALSE;
    }

    if (X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253) < 0) {
        *err = "X509_NAME_print_ex() failed";
        goto failed;
    }

    s->len = (size_t)BIO_pending(bio);
    s->data = znx_pool_malloc(pool, s->len);

    BIO_read(bio, s->data, (int)s->len);

    BIO_free(bio);
    X509_free(cert);

    return ZNX_TRUE;

failed:

    BIO_free(bio);
    X509_free(cert);

    return ZNX_FALSE;
}


znx_bool_t
znx_ssl_get_subject_dn_legacy(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    char        *p;
    size_t      len;
    X509        *cert;
    X509_NAME   *name;

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

    name = X509_get_subject_name(cert);
    if (name == NULL) {
        *err = "X509_get_subject_name() return nil";
        X509_free(cert);
        return ZNX_FALSE;
    }

    p = X509_NAME_oneline(name, NULL, 0);
    if (p == NULL) {
        *err = "X509_NAME_oneline() failed";
        X509_free(cert);
        return ZNX_FALSE;
    }

    for (len = 0; p[len]; len++) { /* void */ }

    s->len = len;
    s->data = znx_pool_malloc(pool, len);

    znx_memcpy(s->data, p, len);

    OPENSSL_free(p);
    X509_free(cert);

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_get_issuer_dn_legacy(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    char        *p;
    size_t      len;
    X509        *cert;
    X509_NAME   *name;

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

    name = X509_get_issuer_name(cert);
    if (name == NULL) {
        *err = "X509_get_issuer_name() return nil";
        X509_free(cert);
        return ZNX_FALSE;
    }

    p = X509_NAME_oneline(name, NULL, 0);
    if (p == NULL) {
        *err = "X509_NAME_oneline() failed";
        X509_free(cert);
        return ZNX_FALSE;
    }

    for (len = 0; p[len]; len++) { /* void */ }

    s->len = len;
    s->data = znx_pool_malloc(pool, len);
    znx_memcpy(s->data, p, len);

    OPENSSL_free(p);
    X509_free(cert);

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_get_serial_number(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    size_t      len;
    X509        *cert;
    BIO         *bio;

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

    bio = BIO_new(BIO_s_mem());
    if (bio == NULL) {
        *err = "BIO_new() failed";
        X509_free(cert);
        return ZNX_FALSE;
    }

    i2a_ASN1_INTEGER(bio, X509_get_serialNumber(cert));
    len = (size_t)BIO_pending(bio);

    s->len = len;
    s->data = znx_pool_malloc(pool, len);

    BIO_read(bio, s->data, (int)len);
    BIO_free(bio);
    X509_free(cert);

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_get_fingerprint(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    X509            *cert;
    unsigned int    len;
    u_char          buf[EVP_MAX_MD_SIZE];

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

    if (!X509_digest(cert, EVP_sha1(), buf, &len)) {
        *err = "X509_digest() failed";
        X509_free(cert);
        return ZNX_FALSE;
    }

    s->len = 2 * len;
    s->data = znx_pool_malloc(pool, 2 * len);

    znx_hex_dump(s->data, buf, len);

    X509_free(cert);

    return ZNX_TRUE;
}





void
znx_ssl_get_client_verify(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s)
{
    X509            *cert;
    long            rc;
    const char      *str;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        ZNX_STR_SET(s, "NONE");
        return;
    }

    X509_free(cert);

    rc = SSL_get_verify_result(c->ssl->ssl);

    if (rc == X509_V_OK) {
        if (znx_ssl_ocsp_get_status(c, &str)) {
            ZNX_STR_SET(s, "SUCCESS");
            return;
        }

    } else {
        str = X509_verify_cert_error_string(rc);
    }

    s->data = znx_pool_malloc(pool, sizeof("FAILED:") - 1 + znx_strlen(str));
    s->len = (size_t)(znx_sprintf(s->data, "FAILED:%s", str) - s->data);
}


znx_bool_t
znx_ssl_get_client_v_start(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    BIO         *bio;
    X509        *cert;
    size_t      len;

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

    bio = BIO_new(BIO_s_mem());
    if (bio == NULL) {
        *err = "BIO_new() failed";
        X509_free(cert);
        return ZNX_FALSE;
    }

#if OPENSSL_VERSION_NUMBER > 0x10100000L
    ASN1_TIME_print(bio, X509_get0_notBefore(cert));
#else
    ASN1_TIME_print(bio, X509_get_notBefore(cert));
#endif

    len = (size_t)BIO_pending(bio);

    s->len = len;
    s->data = znx_pool_malloc(pool, len);

    BIO_read(bio, s->data, (int)len);
    BIO_free(bio);
    X509_free(cert);

    return ZNX_TRUE;
}


znx_bool_t
znx_ssl_get_client_v_end(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    BIO         *bio;
    X509        *cert;
    size_t      len;

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

    bio = BIO_new(BIO_s_mem());
    if (bio == NULL) {
        *err = "BIO_new() failed";
        X509_free(cert);
        return ZNX_FALSE;
    }

#if OPENSSL_VERSION_NUMBER > 0x10100000L
    ASN1_TIME_print(bio, X509_get0_notAfter(cert));
#else
    ASN1_TIME_print(bio, X509_get_notAfter(cert));
#endif

    len = (size_t)BIO_pending(bio);

    s->len = len;
    s->data = znx_pool_malloc(pool, len);

    BIO_read(bio, s->data, (int)len);
    BIO_free(bio);
    X509_free(cert);

    return ZNX_TRUE;
}


static int64_t
znx_ssl_parse_time(
#if OPENSSL_VERSION_NUMBER > 0x10100000L
    const
#endif
    ASN1_TIME *asn1time, char **err)
{
    BIO         *bio;
    char        *value;
    size_t      len;
    int64_t     time;

    /*
     * OpenSSL doesn't provide a way to convert ASN1_TIME
     * into time_t.  To do this, we use ASN1_TIME_print(),
     * which uses the "MMM DD HH:MM:SS YYYY [GMT]" format (e.g.,
     * "Feb  3 00:55:52 2015 GMT"), and parse the result.
     */

    bio = BIO_new(BIO_s_mem());
    if (bio == NULL) {
        *err = "BIO_new() failed";
        return ZNX_FALSE;
    }

    /* fake weekday prepended to match C asctime() format */

    BIO_write(bio, "Tue ", sizeof("Tue ") - 1);
    ASN1_TIME_print(bio, asn1time);
    len = (size_t)BIO_get_mem_data(bio, &value);

    time = znx_parse_http_time((u_char *) value, len);

    BIO_free(bio);

    return time;
}


znx_bool_t
znx_ssl_get_client_v_remain(znx_conn_t *c, znx_pool_t *pool,
    znx_str_t *s, char **err)
{
    X509        *cert;
    int64_t     now, end;

    s->len = 0;

    cert = SSL_get_peer_certificate(c->ssl->ssl);
    if (cert == NULL) {
        return ZNX_TRUE;
    }

#if OPENSSL_VERSION_NUMBER > 0x10100000L
    end = znx_ssl_parse_time(X509_get0_notAfter(cert), err);
#else
    end = znx_ssl_parse_time(X509_get_notAfter(cert), err);
#endif

    if (end == ZNX_FALSE) {
        X509_free(cert);
        return ZNX_TRUE;
    }

    now = znx_usec_2_sec(get_cache_time());

    if (end < now + 86400) {
        ZNX_STR_SET(s, "0");
        X509_free(cert);
        return ZNX_TRUE;
    }

    s->data = znx_pool_calloc(pool, 64);
    s->len = (size_t)(znx_sprintf(s->data, "%T", (end - now) / 86400) - s->data);

    X509_free(cert);

    return ZNX_TRUE;
}


void
znx_ssl_handshake_handler(znx_event_t *event)
{
    znx_conn_t *c;
    c = event->data;

    if (event->timedout) {
        c->ssl->handler(c);
        return;
    }

    if (znx_ssl_handshake(c) == ZNX_AGAIN) {
        return;
    }

    c->ssl->handler(c);
}


znx_result_t
znx_ssl_handshake(znx_conn_t *c)
{
    int             n, sslerr;
    int             err;
    znx_result_t    rc;

    if (c->ssl->in_ocsp) {
        return znx_ssl_ocsp_validate(c);
    }

    ERR_clear_error();

    n = SSL_do_handshake(c->ssl->ssl);

    if (n == 1) {

#ifndef SSL_OP_NO_RENEGOTIATION
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS

        /* initial handshake done, disable renegotiation (CVE-2009-3555) */
        if (c->ssl->ssl->s3 && SSL_is_server(c->ssl->ssl)) {
            c->ssl->ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
        }

#endif
#endif
#endif

        rc = znx_ssl_ocsp_validate(c);

        if (rc == ZNX_FAILED) {
            return ZNX_FAILED;
        }

        if (rc == ZNX_AGAIN) {
            c->read.handler = znx_ssl_handshake_handler;
            c->write.handler = znx_ssl_handshake_handler;
            znx_event_loop_enable_read(c);
            znx_event_loop_enable_write(c);
            return ZNX_AGAIN;
        }

        c->ssl->handshaked = 1;

        return ZNX_SUCCESS;
    }

    sslerr = SSL_get_error(c->ssl->ssl, n);

    if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) {
        c->read.handler = znx_ssl_handshake_handler;
        c->write.handler = znx_ssl_handshake_handler;
        znx_event_loop_enable_read(c);
        znx_event_loop_enable_write(c);

        return ZNX_AGAIN;
    }

    err = (sslerr == SSL_ERROR_SYSCALL) ? errno : 0;
    c->ssl->no_wait_shutdown = 1;
    c->ssl->no_send_shutdown = 1;
    c->read.eof = 1;

    if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
        ZNX_DEF_LOG_ERROR("peer closed connection in SSL handshake");
        return ZNX_FAILED;
    }

    if (c->ssl->handshake_rejected) {
        ZNX_DEF_LOG_ERROR("handshake rejected");
        ERR_clear_error();
        return ZNX_FAILED;
    }

    c->read.error = 1;
    ZNX_DEF_LOG_ERROR("SSL_do_handshake() failed");

    return ZNX_FAILED;
}


znx_syscall_result_t
znx_ssl_read(znx_conn_t *c, void *buff, size_t count)
{
    int                     n, sslerr;
    znx_syscall_result_t    sres;
    int                     err;
    int                     bytes;

    bytes = 0;

    ERR_clear_error();

    /*
     * SSL_read() may return data in parts, so try to read
     * until SSL_read() would return no data
     */

    for ( ;; ) {
        if (count == 0) {
            sres.eno = 0;
            sres.res = bytes;
            return sres;
        }

        n = SSL_read(c->ssl->ssl, buff, (int)count);

        if (n > 0) {
            bytes += n;
            buff += n;
            count -= (size_t)n;
            c->recv_bytes += n;
            continue;
        }

        sslerr = SSL_get_error(c->ssl->ssl, n);

        err = (sslerr == SSL_ERROR_SYSCALL) ? errno : 0;

        if (sslerr == SSL_ERROR_WANT_READ) {
            c->read.ready = 0;
            err = EAGAIN;
            break;
        }
        
        if (sslerr == SSL_ERROR_WANT_WRITE) {
            c->write.ready = 0;
            err = EAGAIN;
            break;
        }

        break;
    }

    if (bytes > 0) {
        sres.eno = 0;
        sres.res = bytes;
        return sres;
    }

    if (err == EAGAIN) {
        sres.eno = EAGAIN;
        sres.res = 0;
        return sres;
    }

    c->ssl->no_wait_shutdown = 1;
    c->ssl->no_send_shutdown = 1;

    sres.eno = err;
    sres.res = 0;

    if (err == 0) {
        c->read.eof = 1;
    } else {
        c->read.error = 1;
    }

    return sres;
}


znx_syscall_result_t
znx_ssl_write(znx_conn_t *c, void *buff, size_t count)
{
    int                     n, sslerr;
    znx_syscall_result_t    sres;
    int                     err;

    ERR_clear_error();

    n = SSL_write(c->ssl->ssl, buff, (int)count);

    if (n > 0) {
        c->sent_bytes += (int64_t)n;
        sres.eno = 0;
        sres.res = n;
        return sres;
    }

    sslerr = SSL_get_error(c->ssl->ssl, n);

    if (sslerr == SSL_ERROR_ZERO_RETURN) {

        /*
         * OpenSSL 1.1.1 fails to return SSL_ERROR_SYSCALL if an error
         * happens during SSL_write() after close_notify alert from the
         * peer, and returns SSL_ERROR_ZERO_RETURN instead,
         * https://git.openssl.org/?p=openssl.git;a=commitdiff;h=8051ab2
         */

        sslerr = SSL_ERROR_SYSCALL;
    }

    err = (sslerr == SSL_ERROR_SYSCALL) ? errno : 0;

    if (sslerr == SSL_ERROR_WANT_WRITE) {
        c->write.ready = 0;
        sres.eno = EAGAIN;
        sres.res = 0;
        return sres;
    }

    if (sslerr == SSL_ERROR_WANT_READ) {
        c->read.ready = 0;
        sres.eno = EAGAIN;
        sres.res = 0;
        return sres;
    }

    c->ssl->no_wait_shutdown = 1;
    c->ssl->no_send_shutdown = 1;

    sres.eno = err;
    sres.res = 0;

    if (err == 0) {
        c->write.eof = 1;
    } else {
        c->write.error = 1;
    }

    return sres;
}


static void
znx_ssl_shutdown_handler(znx_event_t *event)
{
    znx_conn_t  *c;

    c = event->data;

    if (znx_ssl_shutdown(c) == ZNX_AGAIN) {
        return;
    }

    c->ssl->handler(c);
}


znx_result_t
znx_ssl_shutdown(znx_conn_t *c)
{
    int         n, sslerr;
    int         tries;

    znx_ssl_ocsp_cleanup(c);

    if (SSL_in_init(c->ssl->ssl)) {
        /*
         * OpenSSL 1.0.2f complains if SSL_shutdown() is called during
         * an SSL handshake, while previous versions always return 0.
         * Avoid calling SSL_shutdown() if handshake wasn't completed.
         */

        goto done;
    }

#if 0

    // TODO: 什么情况下需要设置这些东西?
    SSL_set_quiet_shutdown(c->ssl->ssl, 1);
    SSL_set_shutdown(c->ssl->ssl, SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN);

#endif

    ERR_clear_error();

    tries = 2;

    for ( ;; ) {
        /*
         * For bidirectional shutdown, SSL_shutdown() needs to be called
         * twice: first call sends the "close notify" alert and returns 0,
         * second call waits for the peer's "close notify" alert.
         */

        n = SSL_shutdown(c->ssl->ssl);

        if (n == 1) {
            goto done;
        }

        if (n == 0 && tries-- > 1) {
            continue;
        }

        /* before 0.9.8m SSL_shutdown() returned 0 instead of -1 on errors */

        sslerr = SSL_get_error(c->ssl->ssl, n);

        if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) {
            c->read.handler = znx_ssl_shutdown_handler;
            c->write.handler = znx_ssl_shutdown_handler;

            if (sslerr == SSL_ERROR_WANT_READ) {
                c->read.ready = 0;
            } else {
                c->write.ready = 0;
            }

            znx_event_loop_enable_read(c);
            znx_event_loop_enable_write(c);

            znx_event_loop_add_timer(&c->read, 3000);

            return ZNX_AGAIN;
        }

        break;
    }

done:
    SSL_free(c->ssl->ssl);

    return ZNX_SUCCESS;
}


